1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.tools.ant.taskdefs;
19
20 import org.apache.tools.ant.BuildException;
21 import org.apache.tools.ant.Project;
22 import org.apache.tools.ant.Task;
23 import org.apache.tools.ant.util.FileUtils;
24 import org.apache.tools.ant.util.JavaEnvUtils;
25
26 import java.io.File;
27 import java.io.FileOutputStream;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.io.PrintStream;
31 import java.net.HttpURLConnection;
32 import java.net.URL;
33 import java.net.URLConnection;
34 import java.util.Date;
35
36 /***
37 * Gets a particular file from a URL source.
38 * Options include verbose reporting, timestamp based fetches and controlling
39 * actions on failures. NB: access through a firewall only works if the whole
40 * Java runtime is correctly configured.
41 *
42 * @since Ant 1.1
43 *
44 * @ant.task category="network"
45 */
46 public class Get extends Task {
47
48 private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
49
50 private URL source;
51 private File dest;
52 private boolean verbose = false;
53 private boolean useTimestamp = false;
54 private boolean ignoreErrors = false;
55 private String uname = null;
56 private String pword = null;
57
58
59
60 /***
61 * Does the work.
62 *
63 * @exception BuildException Thrown in unrecoverable error.
64 */
65 public void execute() throws BuildException {
66
67
68 int logLevel = Project.MSG_INFO;
69 DownloadProgress progress = null;
70 if (verbose) {
71 progress = new VerboseProgress(System.out);
72 }
73
74
75 try {
76 doGet(logLevel, progress);
77 } catch (IOException ioe) {
78 log("Error getting " + source + " to " + dest);
79 if (!ignoreErrors) {
80 throw new BuildException(ioe, getLocation());
81 }
82 }
83 }
84
85 /***
86 * make a get request, with the supplied progress and logging info.
87 * All the other config parameters are set at the task level,
88 * source, dest, ignoreErrors, etc.
89 * @param logLevel level to log at, see {@link Project#log(String, int)}
90 * @param progress progress callback; null for no-callbacks
91 * @return true for a successful download, false otherwise.
92 * The return value is only relevant when {@link #ignoreErrors} is true, as
93 * when false all failures raise BuildExceptions.
94 * @throws IOException for network trouble
95 * @throws BuildException for argument errors, or other trouble when ignoreErrors
96 * is false.
97 */
98 public boolean doGet(int logLevel, DownloadProgress progress)
99 throws IOException {
100 if (source == null) {
101 throw new BuildException("src attribute is required", getLocation());
102 }
103
104 if (dest == null) {
105 throw new BuildException("dest attribute is required", getLocation());
106 }
107
108 if (dest.exists() && dest.isDirectory()) {
109 throw new BuildException("The specified destination is a directory",
110 getLocation());
111 }
112
113 if (dest.exists() && !dest.canWrite()) {
114 throw new BuildException("Can't write to " + dest.getAbsolutePath(),
115 getLocation());
116 }
117
118 if (progress == null) {
119 progress = new NullProgress();
120 }
121 log("Getting: " + source, logLevel);
122 log("To: " + dest.getAbsolutePath(), logLevel);
123
124
125 long timestamp = 0;
126
127 boolean hasTimestamp = false;
128 if (useTimestamp && dest.exists()) {
129 timestamp = dest.lastModified();
130 if (verbose) {
131 Date t = new Date(timestamp);
132 log("local file date : " + t.toString(), logLevel);
133 }
134 hasTimestamp = true;
135 }
136
137
138 URLConnection connection = source.openConnection();
139
140
141 if (hasTimestamp) {
142 connection.setIfModifiedSince(timestamp);
143 }
144
145 if (uname != null || pword != null) {
146 String up = uname + ":" + pword;
147 String encoding;
148
149
150
151 Base64Converter encoder = new Base64Converter();
152 encoding = encoder.encode(up.getBytes());
153 connection.setRequestProperty ("Authorization",
154 "Basic " + encoding);
155 }
156
157
158 connection.connect();
159
160 if (connection instanceof HttpURLConnection) {
161 HttpURLConnection httpConnection
162 = (HttpURLConnection) connection;
163 long lastModified = httpConnection.getLastModified();
164 if (httpConnection.getResponseCode()
165 == HttpURLConnection.HTTP_NOT_MODIFIED
166 || (lastModified != 0 && hasTimestamp
167 && timestamp >= lastModified)) {
168
169
170
171
172 log("Not modified - so not downloaded", logLevel);
173 return false;
174 }
175
176 if (httpConnection.getResponseCode()
177 == HttpURLConnection.HTTP_UNAUTHORIZED) {
178 String message = "HTTP Authorization failure";
179 if (ignoreErrors) {
180 log(message, logLevel);
181 return false;
182 } else {
183 throw new BuildException(message);
184 }
185 }
186
187 }
188
189
190
191
192
193
194
195 InputStream is = null;
196 for (int i = 0; i < 3; i++) {
197
198
199
200 try {
201 is = connection.getInputStream();
202 break;
203 } catch (IOException ex) {
204 log("Error opening connection " + ex, logLevel);
205 }
206 }
207 if (is == null) {
208 log("Can't get " + source + " to " + dest, logLevel);
209 if (ignoreErrors) {
210 return false;
211 }
212 throw new BuildException("Can't get " + source + " to " + dest,
213 getLocation());
214 }
215
216 FileOutputStream fos = new FileOutputStream(dest);
217 progress.beginDownload();
218 boolean finished = false;
219 try {
220 byte[] buffer = new byte[100 * 1024];
221 int length;
222 while ((length = is.read(buffer)) >= 0) {
223 fos.write(buffer, 0, length);
224 progress.onTick();
225 }
226 finished = true;
227 } finally {
228 FileUtils.close(fos);
229 FileUtils.close(is);
230
231
232
233
234 if (!finished) {
235 dest.delete();
236 }
237 }
238 progress.endDownload();
239
240
241
242
243 if (useTimestamp) {
244 long remoteTimestamp = connection.getLastModified();
245 if (verbose) {
246 Date t = new Date(remoteTimestamp);
247 log("last modified = " + t.toString()
248 + ((remoteTimestamp == 0)
249 ? " - using current time instead"
250 : ""), logLevel);
251 }
252 if (remoteTimestamp != 0) {
253 FILE_UTILS.setFileLastModified(dest, remoteTimestamp);
254 }
255 }
256
257
258 return true;
259 }
260
261
262 /***
263 * Set the URL to get.
264 *
265 * @param u URL for the file.
266 */
267 public void setSrc(URL u) {
268 this.source = u;
269 }
270
271 /***
272 * Where to copy the source file.
273 *
274 * @param dest Path to file.
275 */
276 public void setDest(File dest) {
277 this.dest = dest;
278 }
279
280 /***
281 * If true, show verbose progress information.
282 *
283 * @param v if "true" then be verbose
284 */
285 public void setVerbose(boolean v) {
286 verbose = v;
287 }
288
289 /***
290 * If true, log errors but do not treat as fatal.
291 *
292 * @param v if "true" then don't report download errors up to ant
293 */
294 public void setIgnoreErrors(boolean v) {
295 ignoreErrors = v;
296 }
297
298 /***
299 * If true, conditionally download a file based on the timestamp
300 * of the local copy.
301 *
302 * <p>In this situation, the if-modified-since header is set so
303 * that the file is only fetched if it is newer than the local
304 * file (or there is no local file) This flag is only valid on
305 * HTTP connections, it is ignored in other cases. When the flag
306 * is set, the local copy of the downloaded file will also have
307 * its timestamp set to the remote file time.</p>
308 *
309 * <p>Note that remote files of date 1/1/1970 (GMT) are treated as
310 * 'no timestamp', and web servers often serve files with a
311 * timestamp in the future by replacing their timestamp with that
312 * of the current time. Also, inter-computer clock differences can
313 * cause no end of grief.</p>
314 * @param v "true" to enable file time fetching
315 */
316 public void setUseTimestamp(boolean v) {
317 useTimestamp = v;
318 }
319
320
321 /***
322 * Username for basic auth.
323 *
324 * @param u username for authentication
325 */
326 public void setUsername(String u) {
327 this.uname = u;
328 }
329
330 /***
331 * password for the basic authentication.
332 *
333 * @param p password for authentication
334 */
335 public void setPassword(String p) {
336 this.pword = p;
337 }
338
339 /**********************************************************************
340 * BASE 64 encoding of a String or an array of bytes.
341 *
342 * Based on RFC 1421.
343 *
344 *********************************************************************/
345
346 protected static class Base64Converter {
347
348 public final char[] alphabet = {
349 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
350 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
351 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
352 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
353 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
354 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
355 'w', 'x', 'y', 'z', '0', '1', '2', '3',
356 '4', '5', '6', '7', '8', '9', '+', '/'};
357
358 public String encode(String s) {
359 return encode(s.getBytes());
360 }
361
362 public String encode(byte[] octetString) {
363 int bits24;
364 int bits6;
365
366 char[] out = new char[((octetString.length - 1) / 3 + 1) * 4];
367 int outIndex = 0;
368 int i = 0;
369
370 while ((i + 3) <= octetString.length) {
371
372 bits24 = (octetString[i++] & 0xFF) << 16;
373 bits24 |= (octetString[i++] & 0xFF) << 8;
374 bits24 |= octetString[i++];
375
376 bits6 = (bits24 & 0x00FC0000) >> 18;
377 out[outIndex++] = alphabet[bits6];
378 bits6 = (bits24 & 0x0003F000) >> 12;
379 out[outIndex++] = alphabet[bits6];
380 bits6 = (bits24 & 0x00000FC0) >> 6;
381 out[outIndex++] = alphabet[bits6];
382 bits6 = (bits24 & 0x0000003F);
383 out[outIndex++] = alphabet[bits6];
384 }
385 if (octetString.length - i == 2) {
386
387 bits24 = (octetString[i] & 0xFF) << 16;
388 bits24 |= (octetString[i + 1] & 0xFF) << 8;
389 bits6 = (bits24 & 0x00FC0000) >> 18;
390 out[outIndex++] = alphabet[bits6];
391 bits6 = (bits24 & 0x0003F000) >> 12;
392 out[outIndex++] = alphabet[bits6];
393 bits6 = (bits24 & 0x00000FC0) >> 6;
394 out[outIndex++] = alphabet[bits6];
395
396
397 out[outIndex++] = '=';
398 } else if (octetString.length - i == 1) {
399
400 bits24 = (octetString[i] & 0xFF) << 16;
401 bits6 = (bits24 & 0x00FC0000) >> 18;
402 out[outIndex++] = alphabet[bits6];
403 bits6 = (bits24 & 0x0003F000) >> 12;
404 out[outIndex++] = alphabet[bits6];
405
406
407 out[outIndex++] = '=';
408 out[outIndex++] = '=';
409 }
410 return new String(out);
411 }
412 }
413
414 public interface DownloadProgress {
415 /***
416 * begin a download
417 */
418 public void beginDownload();
419
420 /***
421 * tick handler
422 *
423 */
424 public void onTick();
425
426 /***
427 * end a download
428 */
429 public void endDownload();
430 }
431
432 /***
433 * do nothing with progress info
434 */
435 public static class NullProgress implements DownloadProgress {
436
437 /***
438 * begin a download
439 */
440 public void beginDownload() {
441
442 }
443
444 /***
445 * tick handler
446 *
447 */
448 public void onTick() {
449 }
450
451 /***
452 * end a download
453 */
454 public void endDownload() {
455
456 }
457 }
458
459 /***
460 * verbose progress system prints to some output stream
461 */
462 public static class VerboseProgress implements DownloadProgress {
463 private int dots = 0;
464 PrintStream out;
465
466 public VerboseProgress(PrintStream out) {
467 this.out = out;
468 }
469
470 /***
471 * begin a download
472 */
473 public void beginDownload() {
474 dots = 0;
475 }
476
477 /***
478 * tick handler
479 *
480 */
481 public void onTick() {
482 out.print(".");
483 if (dots++ > 50) {
484 out.flush();
485 dots = 0;
486 }
487 }
488
489 /***
490 * end a download
491 */
492 public void endDownload() {
493 out.println();
494 out.flush();
495 }
496 }
497
498 }